﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Diagnostics;

namespace ParsecParser
{
    public class Parser
    {
        public bool IgnoreLineFormatErrors = true;
        private const string FileNameRegexPattern = @"^(^\S+)_(\d{4}-\d{2}-\d{2})_(\d{2}_\d{2}_\d{2}_\d{6})\.txt";
        private const string BaseLineRegexPattern = @"^\[(?<TimeStamp>[^\]]*)\] \[(?<Source>[^\]]*?)\] \[(?<Target>[^\]]*?)\] \[(?<Ability>[^\]]*?)\] \[(?<Effect>.*?)\] \((?<Value>.*?)\)($|( <(?<Threat>[^>]*?)>))";
        private const string TimeStampRegexPattern = @"^(\d{2}):(\d{2}):(\d{2})\.(\d{3})";
        private const string NpcRegexPattern = @"^([^{]*) {(\d*)}:(\d*)";
        private const string CompanionRegexPattern = @"^([^:]*):([^{]*) {(\d*)}";
        private const string EntityRegexPattern = @"^([^{]*) {(\d*)}";
        private const string EffectRegexPattern = @"^([^{]* {\d*}): ([^{]* {\d*})";
        private const string AbsorbRegexPattern = @"\((\d+) ([^\)]*)\)";
        private const string ValueRegexPattern = @"^(\d+)(\*)?( \w+ {\d+})?(\(\w+ {\d+}\))?( -(\w+ {\d+})?)?( \(\d+ \w+ {\d+}\))?$";
        private static Regex FileNameRegex = new Regex(FileNameRegexPattern, RegexOptions.IgnoreCase | RegexOptions.Compiled);
        private static Regex BaseLineRegex = new Regex(BaseLineRegexPattern, RegexOptions.IgnoreCase | RegexOptions.Compiled);
        private static Regex TimeStampRegex = new Regex(TimeStampRegexPattern, RegexOptions.IgnoreCase | RegexOptions.Compiled);
        private static Regex NpcRegex = new Regex(NpcRegexPattern, RegexOptions.IgnoreCase | RegexOptions.Compiled);
        private static Regex CompanionRegex = new Regex(CompanionRegexPattern, RegexOptions.IgnoreCase | RegexOptions.Compiled);
        private static Regex EntityRegex = new Regex(EntityRegexPattern, RegexOptions.IgnoreCase | RegexOptions.Compiled);
        private static Regex EffectRegex = new Regex(EffectRegexPattern, RegexOptions.IgnoreCase | RegexOptions.Compiled);
        private static Regex AbsorbRegex = new Regex(AbsorbRegexPattern, RegexOptions.IgnoreCase | RegexOptions.Compiled);
        private static Regex ValueRegex = new Regex(ValueRegexPattern, RegexOptions.IgnoreCase | RegexOptions.Compiled);
        public Parser()
        {            
        }
        public CombatLogEvent ParseLine(string currentLine, DateTime currentLogDate)
        {
            CombatLogEvent rval = new CombatLogEvent() { RawString = currentLine };
            Match baseLineMatch = BaseLineRegex.Match(currentLine);
            if (baseLineMatch.Success)
            {
                string timeStamp = baseLineMatch.Groups["TimeStamp"].Value;
                string source = baseLineMatch.Groups["Source"].Value;
                string target = baseLineMatch.Groups["Target"].Value;
                string ability = baseLineMatch.Groups["Ability"].Value;
                string effect = baseLineMatch.Groups["Effect"].Value;
                string value = baseLineMatch.Groups["Value"].Value;
                string threat = baseLineMatch.Groups["Threat"].Value;

                ProcessTimeStamp(ref rval, timeStamp, currentLogDate);
                ProcessSource(ref rval, source);
                ProcessTarget(ref rval, target);
                ProcessAbility(ref rval, ability);
                ProcessEffect(ref rval, effect);
                ProcessValue(ref rval, value);
                ProcessThreat(ref rval, threat);
            }
            else if (!IgnoreLineFormatErrors)
            {
                Debug.WriteLine("Base Format Error: " + currentLine);
                throw new Exception("Base Line Pattern Not Matched");
            }
            else
                rval = null;
            return rval;
        }
        public static DateTime ParseDateFromFileName(string filename, bool parseTime)
        {
            try
            {
                DateTime rval = DateTime.Today;
                Match m = FileNameRegex.Match(filename);
                if (m.Success)
                {
                    if (parseTime)
                        rval = Convert.ToDateTime(m.Groups[2].Value + " " + GetTimeString(m.Groups[3].Value));
                    else
                        rval = Convert.ToDateTime(m.Groups[2].Value);
                }
                return rval;
            }
            catch
            {
                return DateTime.Today;
            }
        }
        public static string GetTimeString(string s)
        {
            if (!string.IsNullOrEmpty(s))
            {
                int lastUnderscore = s.LastIndexOf('_');
                s = s.Substring(0, lastUnderscore).Replace('_', ':');// +',' + s.Substring(lastUnderscore + 1);
            }
            return s;
        }
        private void ProcessTimeStamp(ref CombatLogEvent e, string s, DateTime currentLogDate)
        {
            DateTime rval = currentLogDate;
            Match m = TimeStampRegex.Match(s);
            if (m.Success)
            {
                int hour = Convert.ToInt32(m.Groups[1].Value);
                int min = Convert.ToInt32(m.Groups[2].Value);
                int sec = Convert.ToInt32(m.Groups[3].Value);
                int ms = Convert.ToInt32(m.Groups[4].Value);
                rval = new DateTime(currentLogDate.Year, currentLogDate.Month, currentLogDate.Day, hour, min, sec, ms);
                if (rval.CompareTo(currentLogDate) < 0) //handle over midnight date issue
                {
                    rval = rval.AddDays(1);
                }
            }
            e.TimeStamp = rval;
        }
        private void ProcessSource(ref CombatLogEvent e, string s)
        {
            e.Source = ProcessParticipant(s);
        }
        private void ProcessTarget(ref CombatLogEvent e, string s)
        {
            e.Target = ProcessParticipant(s);
        }
        private void ProcessAbility(ref CombatLogEvent e, string s)
        {
            if (!string.IsNullOrEmpty(s))
                e.Ability = ProcessEntity(s);
        }
        private void ProcessEffect(ref CombatLogEvent e, string s)
        {
            if (!string.IsNullOrEmpty(s))
            {
                Match m = EffectRegex.Match(s);
                if (m.Success)
                {
                    e.EffectType = ProcessEntity(m.Groups[1].Value);
                    e.EffectName = ProcessEntity(m.Groups[2].Value);
                }
            }
        }
        private CombatLogParticipant ProcessParticipant(string s)
        {
            CombatLogParticipant rval = new CombatLogParticipant(CombatLogParticipant.EmptyParticipantName, 0, 0, s.StartsWith("@"));
            string name = null;
            if (rval.IsPlayer)
            {
                if (s.EndsWith("}"))
                {
                    Match m = CompanionRegex.Match(s.Substring(1));
                    if (m.Success)
                    {
                        name = m.Groups[2].Value;
                        rval.IsPlayerCompanion = true;
                        rval.CompanionOwner = m.Groups[1].Value;
                        rval.EntityId = Convert.ToInt64(m.Groups[3].Value);
                    }
                }
                else
                {
                    name = s.Substring(1);
                }
            }
            else
            {
                Match m = NpcRegex.Match(s);
                if (m.Success)
                {
                    name = m.Groups[1].Value;
                    rval.EntityId = Convert.ToInt64(m.Groups[2].Value);
                    rval.UniqueId = Convert.ToInt64(m.Groups[3].Value);
                }
            }
            if (!string.IsNullOrEmpty(name))
                rval.DisplayName = name;
            return rval;
        }
        private CombatLogEntity ProcessEntity(string s)
        {
            CombatLogEntity rval = new CombatLogEntity(CombatLogEntity.EmptyEntityName, 0);
            Match m = EntityRegex.Match(s);
            if (m.Success)
            {
                string n = m.Groups[1].Value;
                if (!string.IsNullOrEmpty(n))
                    rval.DisplayName = n;
                rval.EntityId = Convert.ToInt64(m.Groups[2].Value);
            }
            return rval;
        }
        private void ProcessValue(ref CombatLogEvent e, string s)
        {
            if (!string.IsNullOrEmpty(s))
            {
                Match m = ValueRegex.Match(s);
                if (m.Success)
                {
                    e.Value = Convert.ToInt32(m.Groups[1].Value);
                    e.IsCrit = !string.IsNullOrEmpty(m.Groups[2].Value);
                    string damageTypeString = m.Groups[3].Value.Trim();
                    if (!string.IsNullOrEmpty(damageTypeString))
                    {
                        e.DamageType = ProcessEntity(damageTypeString);
                    }
                    string reflectString = m.Groups[4].Value.Trim();
                    if (!string.IsNullOrEmpty(reflectString))
                    {
                        //reflected?
                        e.DamageModifier = ProcessEntity(reflectString.Substring(1,reflectString.LastIndexOf("}")));
                    }
                    string mitigationString = m.Groups[5].Value.Trim();
                    if (!string.IsNullOrEmpty(mitigationString))
                    {
                        e.Mitigation = ProcessEntity(mitigationString.Substring(1));
                    }
                    string absorbString = m.Groups[7].Value.Trim();
                    if (!string.IsNullOrEmpty(absorbString))
                    {
                        Match absorbMatch = AbsorbRegex.Match(absorbString);
                        if (absorbMatch.Success)
                        {
                            e.AbsorbedValue = Convert.ToInt32(absorbMatch.Groups[1].Value);
                            e.AbsorbType = ProcessEntity(absorbMatch.Groups[2].Value);
                        }
                    }
                }
            }
        }
        private void ProcessThreat(ref CombatLogEvent e, string s)
        {
            if (!string.IsNullOrEmpty(s))
            {
                e.Threat = Convert.ToInt32(s);
            }
        }
    }
}
